﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Microsoft.TeamFoundation.Client;
using Microsoft.TeamFoundation.Common;
using Microsoft.TeamFoundation.Framework.Client;
using Microsoft.TeamFoundation.Framework.Server;
using Microsoft.TeamFoundation.Framework.Server.Alm;
using Microsoft.TeamFoundation.VersionControl.Client;
using Microsoft.TeamFoundation.VersionControl.Server;
using Microsoft.TeamFoundation.WorkItemTracking.Client;
using Changeset = Microsoft.TeamFoundation.VersionControl.Server.Changeset;
using ClientChangeset = Microsoft.TeamFoundation.VersionControl.Client.Changeset;
using ChangesetVersionSpec = Microsoft.TeamFoundation.VersionControl.Client.ChangesetVersionSpec;
using MergeSource = Microsoft.TeamFoundation.VersionControl.Client.MergeSource;
using RecursionType = Microsoft.TeamFoundation.VersionControl.Client.RecursionType;

namespace Inmeta.TFS.MergeWorkItemsEventHandler
{
    public class MergeWorkItemsEventHandler : ISubscriber
    {
        #region ISubscriber members

        public Type[] SubscribedTypes()
        {
            return new[] {typeof (CheckinNotification)};
        }

        public string Name
        {
            get { return "Inmeta.TFS.MergeWorkItemsEventHandler"; }
        }

        public SubscriberPriority Priority
        {
            get { return SubscriberPriority.Normal; }
        }

        #endregion

        public EventNotificationStatus ProcessEvent(TeamFoundationRequestContext requestContext,
                                                    NotificationType notificationType, object notificationEventArgs,
                                                    out int statusCode, out string statusMessage,
                                                    out ExceptionPropertyCollection properties)
        {
            statusCode = 0;
            properties = null;
            statusMessage = String.Empty;
            try
            {
                if (notificationType == NotificationType.Notification &&
                    notificationEventArgs is CheckinNotification)
                {
                    if (ShouldPerformMerge(requestContext))
                    {
                        var ev = notificationEventArgs as CheckinNotification;
                        Changeset cs = requestContext.GetChangeset(ev.Changeset);
                        if (cs != null)
                        {
                            var tpc = requestContext.GetImpersonatedCollection(cs.Committer);
                            MergeWorkItems(tpc, cs.ChangesetId);
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                TeamFoundationApplicationCore.LogException(
                    "Inmeta.TFS.MergeWorkItemEventHandler encountered an exception", ex);
            }
            return EventNotificationStatus.ActionPermitted;
        }

        private bool ShouldPerformMerge(TeamFoundationRequestContext requestContext)
        {
            var collection = requestContext.GetCollection();
            var registryService = collection.GetService<ITeamFoundationRegistry>();

            var enabled = registryService.GetValue<bool?>("/Inmeta/MergeEventHandler/Enabled");
            if (!enabled.HasValue)
                return true;

            return enabled.Value;
        }

        public void MergeWorkItems(TfsTeamProjectCollection tpc, int changesetId)
        {
            var vcs = (VersionControlServer) tpc.GetService(typeof (VersionControlServer));
            var changes = vcs.GetChangesForChangeset(changesetId, false, Int32.MaxValue, null, null, true);
            var associatedWorkItems = new List<int>();

            foreach (var change in changes.PendingMerges())
            {
                foreach (var ms in change.MergeSources)
                {
                    IEnumerable history = GetMergeHistory(vcs, ms);

                    foreach (var wi in from ClientChangeset cs in history
                                       from wi in cs.WorkItems.Where(wi => !associatedWorkItems.Any(w => w == wi.Id))
                                       select wi)
                    {
                        associatedWorkItems.Add(wi.Id);
                    }
                }
            }
            AddWorkItemsToChangeset(tpc, changesetId, associatedWorkItems);
        }

        private static IEnumerable GetMergeHistory(VersionControlServer vcs, MergeSource ms)
        {
            var from = new ChangesetVersionSpec(ms.VersionFrom);
            var to = new ChangesetVersionSpec(ms.VersionTo);
            return vcs.QueryHistory(ms.ServerItem, from, 0, RecursionType.Full, null,
                                    from, to, Int32.MaxValue,
                                    true, true);
        }

        public void AddWorkItemsToChangeset(TfsTeamProjectCollection tpc, int changeSetId, List<int> workItems)
        {
            if (workItems.Count == 0)
                return;

            var store = (WorkItemStore) tpc.GetService(typeof (WorkItemStore));
            var vcs = (VersionControlServer) tpc.GetService(typeof (VersionControlServer));

            foreach (int wi in workItems)
            {
                WorkItem from = store.GetWorkItem(wi);

                RegisteredLinkType changeset = store.RegisteredLinkTypes["Fixed in Changeset"];
                ClientChangeset ch = vcs.GetChangeset(changeSetId);

                string comment = "Automatically associated with changeset " + changeSetId;

                var el1 = new ExternalLink(changeset, ch.ArtifactUri.AbsoluteUri) {Comment = ch.Comment};

                //Add the link as related link if it does not already exist
                if (!from.Links.ContainsArtifact(el1.LinkedArtifactUri))
                {
                    from.Links.Add(el1);
                    from.History = comment;
                    from.Save();
                }
            }
        }
    }
}

